package edu.ua.cs.cs515.gcj.tetris;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

/**
 * Contains all data regarding our tetris game and notifies the view whenever a change has
 * been made to the data.
 */
public class TetrisModel {
	
	public static final int EASY_DELAY = 600;
	public static final int MEDIUM_DELAY = 300;
	public static final int HARD_DELAY = 150;
	public static final int INSANE_DELAY = 50;
	
	// Transparent composite effect
	private final AlphaComposite TRANSPARENT = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);

	// Our default x location is 4
	private final int DEFAULT_X = 4;
	// Tetris has a default size of 10x20 blocks
	private final int NUM_BLOCKS_X = 10;
	private final int NUM_BLOCKS_Y = 20;
	
	// Block positions (x,y) of the current Tetromino
	private Point[] blockPositions = new Point[4];
	
	// True if the tetromino is currently moving.  False otherwise
	private boolean tetrominoActive = false;
	
	// The matrix.  Contains information regarding block locations and colors
	private char[][] matrix = new char[NUM_BLOCKS_Y][NUM_BLOCKS_X];
	
	// A reference to the view
	private TetrisView view;
		
	// The current tetromino, the next tetromino and the tetromino factory
	private Tetromino tetromino;
	private Tetromino nextTetromino;
	private TetrominoFactory tetrominoFactory;
	
	// Custom blue color
	private Color LightBlue = new Color(0x0066FF);
	
	// A reference to the audio manager object
	private AudioManager audioManager;
	
	// The current speedUp.  Modifies the amount of time the game loop sleeps
	private int speedUp = 0;
	
	// The current score
	private int score = 0;
	
	// The position of the current tetromino
	private int xPos = DEFAULT_X;
	private int yPos = -1;
		
	private BufferedImage board;
	
	// Reference to the game loop runnable
	private GameLoop gameLoop;
	
	// The difficulty level
	// 'E' => Easy		Slowest and shows a preview of where the piece will land
	// 'M' => Medium    Slightly faster with no preview
	// 'H' => Hard      Fast
	// 'I' => Insane    Ridiculous
	private char difficulty = 'M';
	
	// Constructor, accepts a view as a parameter
	public TetrisModel(TetrisView view) {
		this.view = view;
		this.tetrominoFactory = new TetrominoFactory();
		this.audioManager = AudioManager.getInstance();
		clearMatrix();
	}
	
	public int getScore() {
		return this.score;
	}
	
	public void setDifficulty(char difficulty) {
		if (difficulty == 'E' || difficulty == 'M' || difficulty == 'H' || difficulty == 'I') {
			this.difficulty = difficulty;
		}
	}
	
	public void muteSound(boolean muted) {
		audioManager.setMuted(muted, gameLoop.isGameStarted() && !gameLoop.isPaused());
	}
	
	public char getDifficulty() {
		return this.difficulty;
	}
	
	// Rotates the tetromino
	public void rotateTetromino() {
		if (canTetrominoRotate()) {
			this.tetromino.rotateLeft();
			initBlockPositions();
			generateScene();
		}
	}
	
	// Moves the tetromino right, if possible
	public void incrementPosX() {
		if (canTetrominoMoveRight()) {
			for (int i=0;i<4;i++) {
				blockPositions[i].x++;
			}
			this.xPos++;
			generateScene();
		}
	}
	
	// Moves the tetromino left, if possible
	public void decrementPosX() {
		if (canTetrominoMoveLeft()) {
			for (int i=0;i<4;i++) {
				blockPositions[i].x--;
			}
			this.xPos--;
			generateScene();
		}
	}
	
	// Determines whether or not a tetromino can rotate.  This is achieved by requesting
	// a rotation from the tetromino object and then testing whether that rotation causes
	// a collision.  If not, return true.  Otherwise, return false.
	public boolean canTetrominoRotate() {
		if (!gameLoop.isGameStarted() || gameLoop.isPaused()) {
			return false;
		}
		boolean result = true;
		int[][] rotatedMatrix = tetromino.getRotatedMatrix();
		for (int row=0;row<rotatedMatrix.length;row++) {
			for (int col=0;col<rotatedMatrix[row].length;col++) {
				if (rotatedMatrix[row][col]==1) {
					if (row+yPos > NUM_BLOCKS_Y-1 || col+xPos > NUM_BLOCKS_X-1 || matrix[row+yPos][col+xPos]!=0) {
						result = false;
						break;
					}
				}
			}
			if (!result) {
				break;
			}
		}
		return result;
	}
	
	// Determines whether or not the tetromino can move right by testing the
	// boundaries of the game as well as the contents of the matrix
	public boolean canTetrominoMoveRight() {
		if (!gameLoop.isGameStarted() || gameLoop.isGameOver() || gameLoop.isPaused()) {
			return false;
		}
		for (int i=0;i<4;i++) {
			if (blockPositions[i].y < 0 || blockPositions[i].x+1 > NUM_BLOCKS_X-1 || 
			    blockPositions[i].x+1 < 0 || matrix[blockPositions[i].y][blockPositions[i].x+1] != 0) {
				return false;
			}
		}
		return true;
	}
	
	// Determines whether or not the tetromino can move left by testing the
	// boundaries of the game as well as the contents of the matrix
	public boolean canTetrominoMoveLeft() {
		if (!gameLoop.isGameStarted() || gameLoop.isGameOver() || gameLoop.isPaused()) {
			return false;
		}
		for (int i=0;i<4;i++) {
			if (blockPositions[i].y < 0 || blockPositions[i].x-1 > NUM_BLOCKS_X-1 ||
				blockPositions[i].x-1 < 0 || matrix[blockPositions[i].y][blockPositions[i].x-1] != 0) {
				return false;
			}
		}
		return true;
	}
	
	// Sets the speed up
	public void setSpeedUp(int speedUp) {
		this.speedUp = speedUp;
	}
	
	// Moves the tetromino down by 1 block location
	private void incrementPosY() {
		for (int i=0;i<4;i++) {
			blockPositions[i].y++;
		}
		yPos++;
	}
	
	// Saves the current tetromino into the matrix
	private void addTetrominoToMatrix() {
		for (int i=0;i<4;i++) {
			matrix[blockPositions[i].y][blockPositions[i].x] = tetromino.getColor();
		}
	}
	
	// Initializes the game by requesting a tetromino and starting the game loop
	public void initializeGame() {
		if (gameLoop == null) {
			nextTetromino = tetrominoFactory.selectTetromino(0);
			gameLoop = new GameLoop();
			Thread t = new Thread(gameLoop);
			t.start();
		}
	}
	
	// Triggers the game to begin
	public void triggerGame() {
		clearMatrix();
		tetrominoActive = false;
		score = 0;
		gameLoop.triggerGame();
	}
	
	public void endGame() {
		if (gameLoop.isGameStarted()) {
			triggerGame();
			generateScene();
		}
	}
	
	public boolean isGameActive() {
		if (gameLoop != null) {
			return gameLoop.gameStarted;
		}
		return false;
	}
	
	// Triggers the game to pause or resume
	public void pauseResumeGame() {
		gameLoop.pause();
	}
	
	// Drops the current block
	public void dropBlock() {
		while (canTetrominoMoveDown()) { incrementPosY(); }
	}
	
	// Clears the matrix
	private void clearMatrix() {
		for (int row=0;row<matrix.length;row++) {
			for (int col=0;col<matrix[0].length;col++) {
				matrix[row][col] = 0;
			}
		}
	}
	
	// Determines whether or not the tetromino can move down by testing the bounds of
	// the game as well as the contents of the matrix.
	private boolean canTetrominoMoveDown() {
		if (!gameLoop.isGameStarted() || gameLoop.isGameOver() || gameLoop.isPaused()) {
			return false;
		}
		boolean result = true;
		for (int i=0;i<4;i++) {
			if (blockPositions[i].y+1 > NUM_BLOCKS_Y-1) {
				result = false;
				break;
			}
			if (matrix[blockPositions[i].y+1][blockPositions[i].x] != 0) {
				result = false;
			}
		}
		return result;
	} 
	
	// Searches for solid rows and "destroys them".  Plays a sound effect when
	// a row is destroyed as well as another sound effect if the user successfully
	// destroys 3 or more rows at once.
	private void searchAndDestroy() {
		int numFound=0;
		for (int row=0;row<matrix.length;row++) {
			boolean found = true;
			for (int col=0;col<matrix[0].length;col++) {
				if (matrix[row][col] == 0) {
					found = false;
					break;
				}
			}
			if (found) {
				audioManager.playExplosion();
				for (int j = 0; j < matrix[0].length; j++) {
					matrix[row][j] = 'X';
					generateScene();
					try {
						Thread.sleep(15);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				dropRow(row);
				score+=100;
				numFound++;
			}
		}
		// Bonus!
		if (numFound > 2) {
			score+=300;
			audioManager.playBonus();
		}
	}
	
	// Moves all rows above 'row' down by 1 position
	private void dropRow(int row) {
		for (int i = row; i > 0; i--) {
			for (int j = 0; j < matrix[i].length; j++) {
				matrix[i][j] = matrix[i - 1][j];
			}
		}
	}
	
	
	// Generates the current scene by rendering the contents of the matrix and overlaying
	// the current tetromino.  Some special graphics effects were added to make it look
	// nicer.
	private void generateScene() {
		
		Graphics2D g2d = view.getSceneGraphcs();
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
		g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);

		int blockSize = Math.min(view.getViewHeight()/NUM_BLOCKS_Y, view.getViewWidth()/NUM_BLOCKS_X);
			
		if (this.board == null || board.getWidth() != NUM_BLOCKS_X * blockSize || board.getHeight() != NUM_BLOCKS_Y * blockSize) {
			board = new BufferedImage(NUM_BLOCKS_X * blockSize, NUM_BLOCKS_Y * blockSize, BufferedImage.TYPE_INT_ARGB);
		}
		drawBoard(board.createGraphics(), blockSize);
		g2d.drawImage(board, 0, 0,board.getWidth(),view.getViewHeight(),null);
	
		g2d.setColor(Color.WHITE);
		g2d.drawLine(NUM_BLOCKS_X * blockSize, 0, NUM_BLOCKS_X * blockSize, view.getViewHeight());
		g2d.setColor(Color.GRAY);
		g2d.drawLine(NUM_BLOCKS_X * blockSize+1, 0, NUM_BLOCKS_X * blockSize+1, view.getViewHeight());
		
		Font font = g2d.getFont();
		g2d.setFont(new Font(font.getName(), font.getStyle(), 18));
		
		g2d.setColor(Color.BLACK);
		g2d.fillRect(blockSize * (NUM_BLOCKS_X + 1), blockSize, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2), view.getViewHeight()/3);
		g2d.setColor(Color.GRAY);
		g2d.drawRect(blockSize * (NUM_BLOCKS_X + 1), blockSize, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2), view.getViewHeight()/3);
		g2d.setColor(Color.WHITE);
		g2d.drawRect(blockSize * (NUM_BLOCKS_X + 1)+1, blockSize+1, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2)-2, view.getViewHeight()/3-2);
		
		int nextTetrominoWidth = g2d.getFontMetrics().stringWidth("Next Tetromino:");
		while (nextTetrominoWidth > view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2)-5) {
			g2d.setFont(new Font(g2d.getFont().getName(),g2d.getFont().getStyle(),g2d.getFont().getSize()-1));
			nextTetrominoWidth = g2d.getFontMetrics().stringWidth("Next Tetromino:");
		}
		g2d.drawString("Next Tetromino:", blockSize * (NUM_BLOCKS_X + 1) + 5, blockSize + 20);
		
		g2d.setColor(Color.MAGENTA);
		
		if (gameLoop.isGameStarted()) {
			int[][] nextTetrominoMatrix = nextTetromino.getShapeMatrix();
			g2d.setColor(getColorFromChar(nextTetromino.getColor()));
			Point p;
			for (int row=0;row<nextTetrominoMatrix.length;row++) {
				for (int col=0;col<nextTetrominoMatrix[row].length;col++) {
					if (nextTetrominoMatrix[row][col] == 1) {
						p = new Point(blockSize * (NUM_BLOCKS_X + 1) + (col - nextTetromino.getLeftWhiteSpace()) * blockSize + (view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2))/2 - (nextTetromino.getMaxWidth() * blockSize)/2, blockSize + (row - nextTetromino.getTopWhiteSpace()) * blockSize + view.getViewHeight()/6 - (nextTetromino.getMaxHeight() * blockSize)/2);
						drawStyledBlock(g2d, getColorFromChar(nextTetromino.getColor()), blockSize, p, false);
					}
				}
			}
		} else {
			g2d.setFont(new Font(g2d.getFont().getName(),g2d.getFont().getStyle(),50));
			g2d.setColor(Color.WHITE);
			int y = blockSize + view.getViewHeight()/6 + g2d.getFontMetrics().getHeight()/4 + 10;
			while (y < blockSize + view.getViewHeight()/3 - 40) {
				g2d.setFont(new Font(g2d.getFont().getName(),g2d.getFont().getStyle(),g2d.getFont().getSize()+1));
				y = blockSize + view.getViewHeight()/6 + g2d.getFontMetrics().getHeight()/4 + 10;
			}
			g2d.drawString("?", blockSize * (NUM_BLOCKS_X + 1) + (view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2))/2 - g2d.getFontMetrics().stringWidth("?")/2, y);
		}
		
		g2d.setColor(Color.LIGHT_GRAY);
		g2d.fillRect(blockSize * (NUM_BLOCKS_X + 1), blockSize + view.getViewHeight()/3 + 10, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2), 132);
		g2d.setColor(Color.GRAY);
		g2d.drawRect(blockSize * (NUM_BLOCKS_X + 1), blockSize + view.getViewHeight()/3 + 10, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2), 132);
		g2d.drawLine(blockSize * (NUM_BLOCKS_X + 1)+1, blockSize + view.getViewHeight()/3 + 44, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2) + blockSize * (NUM_BLOCKS_X + 1) - 2, blockSize + view.getViewHeight()/3 + 44);
		g2d.setColor(Color.WHITE);
		g2d.drawRect(blockSize * (NUM_BLOCKS_X + 1)+1, blockSize + view.getViewHeight()/3 + 11, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2) - 2, 130);
		g2d.drawLine(blockSize * (NUM_BLOCKS_X + 1)+1, blockSize + view.getViewHeight()/3 + 45, view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2) + blockSize * (NUM_BLOCKS_X + 1) - 2, blockSize + view.getViewHeight()/3 + 45);
		g2d.setFont(new Font(g2d.getFont().getName(),g2d.getFont().getStyle(),22));
		g2d.setColor(Color.BLACK);
		g2d.drawString("Score: " + score, blockSize * (NUM_BLOCKS_X + 1) + 5, blockSize + view.getViewHeight()/3 + g2d.getFont().getSize() + 15);
		g2d.setFont(new Font(g2d.getFont().getName(),g2d.getFont().getStyle(),18));
		g2d.drawString("Controls:",blockSize * (NUM_BLOCKS_X + 1) + 5,blockSize + view.getViewHeight()/3 + g2d.getFont().getSize() + 47);
		g2d.setFont(new Font(g2d.getFont().getName(),g2d.getFont().getStyle(),12));
		g2d.drawString("Up arrow - Rotate",blockSize * (NUM_BLOCKS_X + 1) + 5,blockSize + view.getViewHeight()/3 + g2d.getFont().getSize()*2 + 56);
		g2d.drawString("Left arrow - Move left",blockSize * (NUM_BLOCKS_X + 1) + 5,blockSize + view.getViewHeight()/3 + g2d.getFont().getSize()*3 + 58);
		g2d.drawString("Right arrow - Move right",blockSize * (NUM_BLOCKS_X + 1) + 5,blockSize + view.getViewHeight()/3 + g2d.getFont().getSize()*4 + 60);
		g2d.drawString("Down arrow - Speed up",blockSize * (NUM_BLOCKS_X + 1) + 5,blockSize + view.getViewHeight()/3 + g2d.getFont().getSize()*5 + 62);
		g2d.drawString("Space bar - Drop block",blockSize * (NUM_BLOCKS_X + 1) + 5,blockSize + view.getViewHeight()/3 + g2d.getFont().getSize()*6 + 64);


		view.setButtonArea(new Rectangle(blockSize * (NUM_BLOCKS_X + 1), view.getViewHeight() - 60,view.getViewWidth() - blockSize * (NUM_BLOCKS_X + 2),50));
		view.render();
		g2d.dispose();
		
	}
	
	// Draws the checkerboard pattern as well as the current tetromino
	private void drawBoard(Graphics2D g2d, int blockSize) {
		Composite opaque = g2d.getComposite();
		
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
		g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);

		for (int row=0;row<NUM_BLOCKS_Y;row++) {
			for (int col=0;col<NUM_BLOCKS_X;col++) {
				if (matrix[row][col] == 0) {
					if (row % 2 == 0) {
						if (col % 2 == 0) {
							g2d.setColor(Color.BLACK);
						} else {
							g2d.setColor(Color.DARK_GRAY);
						}
					} else {
						if (col % 2 == 0) {
							g2d.setColor(Color.DARK_GRAY);
						} else {
							g2d.setColor(Color.BLACK);
						}
					}
					g2d.fillRect(col * blockSize, row * blockSize, blockSize, blockSize);
				} else {
					drawStyledBlock(g2d, getColorFromChar(matrix[row][col]), blockSize, new Point(col, row), true);
				}
			}
		}
		if (tetrominoActive) {
			for (int i=0;i<4;i++) {
				drawStyledBlock(g2d, getColorFromChar(tetromino.getColor()), blockSize, blockPositions[i], true);
			}
			boolean foundHitPoint = false;
			Point[] previewPoints = new Point[4];
			int count = 1;
			while (!foundHitPoint) {
				for (int i=0;i<4;i++) {
					previewPoints[i] = new Point(blockPositions[i].x,blockPositions[i].y+count);
					if (previewPoints[i].y > NUM_BLOCKS_Y - 1|| matrix[previewPoints[i].y][previewPoints[i].x] != 0) {
						foundHitPoint = true;
					}
				}
				count++;
			}
			if (difficulty == 'E' || difficulty == 'M') {
				g2d.setColor(Color.WHITE);
				for (int i=0;i<4;i++) {
					g2d.setComposite(TRANSPARENT);
					g2d.fillRect(previewPoints[i].x * blockSize, (previewPoints[i].y-1) * blockSize, blockSize, blockSize);
					g2d.setComposite(opaque);
					g2d.drawRect(previewPoints[i].x * blockSize, (previewPoints[i].y-1) * blockSize, blockSize, blockSize);
					g2d.drawRect(previewPoints[i].x * blockSize+1, (previewPoints[i].y-1) * blockSize+1, blockSize-2, blockSize-2);
				}
			}
		}
		Font font = g2d.getFont();
		g2d.setFont(new Font(font.getName(), font.getStyle(), 32));
		g2d.setColor(Color.WHITE);
		if (gameLoop.isPaused()) {
			String out = "Paused";
			int stringWidth = g2d.getFontMetrics(g2d.getFont()).stringWidth(out);
			g2d.setComposite(TRANSPARENT);
			g2d.setColor(LightBlue);
			g2d.fillRect(NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2 - 5, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2-g2d.getFontMetrics().getHeight()+5, stringWidth+10, g2d.getFontMetrics().getHeight()+10);
			g2d.setComposite(opaque);
			g2d.drawRect(NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2 - 5, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2-g2d.getFontMetrics().getHeight()+5, stringWidth+10, g2d.getFontMetrics().getHeight()+10);
			g2d.drawRect(NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2 - 4, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2-g2d.getFontMetrics().getHeight()+6, stringWidth+8, g2d.getFontMetrics().getHeight()+8);
			g2d.setColor(Color.WHITE);
			g2d.drawString(out, NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2);
		} else if (gameLoop.isGameOver()) {
			String out = "Game Over";
			int stringWidth = g2d.getFontMetrics(g2d.getFont()).stringWidth(out);
			g2d.setComposite(TRANSPARENT);
			g2d.setColor(LightBlue);
			g2d.fillRect(NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2 - 5, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2-g2d.getFontMetrics().getHeight()+5, stringWidth+10, g2d.getFontMetrics().getHeight()+10);
			g2d.setComposite(opaque);
			g2d.drawRect(NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2 - 5, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2-g2d.getFontMetrics().getHeight()+5, stringWidth+10, g2d.getFontMetrics().getHeight()+10);
			g2d.drawRect(NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2 - 4, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2-g2d.getFontMetrics().getHeight()+6, stringWidth+8, g2d.getFontMetrics().getHeight()+8);
			g2d.setColor(Color.WHITE);
			g2d.drawString(out, NUM_BLOCKS_X * blockSize / 2 - stringWidth / 2, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2);
		} else if (!gameLoop.isGameStarted()) {
			String outFirst = "Select a difficulty";
			String outLast = "And press Start Game!";
			int stringWidthFirst = g2d.getFontMetrics(g2d.getFont()).stringWidth(outFirst);
			int stringWidthLast = g2d.getFontMetrics(g2d.getFont()).stringWidth(outLast);
			while (stringWidthFirst > NUM_BLOCKS_X * blockSize) {
				g2d.setFont(new Font(font.getName(), font.getStyle(), g2d.getFont().getSize()-1));
				stringWidthFirst = g2d.getFontMetrics(g2d.getFont()).stringWidth(outFirst);
			}
			g2d.drawString(outFirst, NUM_BLOCKS_X * blockSize / 2 - stringWidthFirst / 2, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2);
			int firstHeight = g2d.getFontMetrics().getHeight();
			g2d.setFont(new Font(font.getName(), font.getStyle(), 32));
			while (stringWidthLast > NUM_BLOCKS_X * blockSize) {
				g2d.setFont(new Font(font.getName(), font.getStyle(), g2d.getFont().getSize()-1));
				stringWidthLast = g2d.getFontMetrics(g2d.getFont()).stringWidth(outLast);
			}
			g2d.drawString(outLast, NUM_BLOCKS_X * blockSize / 2 - stringWidthLast / 2, NUM_BLOCKS_Y * blockSize / 2 - g2d.getFontMetrics().getHeight()/2 + firstHeight);
		}
	}
	
	// Draws a stylized block
	private void drawStyledBlock(Graphics2D g2d, Color color, int blockSize, Point position, boolean useBlockSizeInPosition) {
		Composite opaque = g2d.getComposite();
		
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
		g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);

		int scale = 1;
		if (useBlockSizeInPosition) {
			scale = blockSize;
		}
	
		g2d.setColor(color);
		g2d.fillRect(position.x * scale, position.y * scale, blockSize, blockSize);
		g2d.setColor(color.brighter());
		g2d.drawLine(position.x * scale + 1, position.y * scale + 1, position.x * scale + blockSize - 2, position.y * scale + 1);
		g2d.drawLine(position.x * scale + 1, position.y * scale + 1, position.x * scale + 1, position.y * scale + blockSize - 2);
		g2d.drawLine(position.x * scale + 2, position.y * scale + 2, position.x * scale + blockSize - 3, position.y * scale + 2);
		g2d.drawLine(position.x * scale + 2, position.y * scale + 2, position.x * scale + 2, position.y * scale + blockSize - 3);
		g2d.setColor(color.darker());
		g2d.drawLine(position.x * scale + 1, position.y * scale + blockSize - 2, position.x * scale + blockSize - 2, position.y * scale + blockSize - 2);
		g2d.drawLine(position.x * scale + blockSize - 2, position.y * scale + 1, position.x * scale + blockSize - 2, position.y * scale + blockSize - 2);
		g2d.drawLine(position.x * scale + 2, position.y * scale + blockSize - 3, position.x * scale + blockSize - 3, position.y * scale + blockSize - 3);
		g2d.drawLine(position.x * scale + blockSize - 3, position.y * scale + 2, position.x * scale + blockSize - 3, position.y * scale + blockSize - 3);
		g2d.setColor(Color.WHITE);
		g2d.setClip(new Rectangle(position.x * scale,position.y * scale,blockSize,blockSize));
		g2d.setComposite(TRANSPARENT);
		g2d.fillArc(position.x * scale - blockSize - 6, position.y * scale - blockSize - 6, blockSize*2 - 6, blockSize*2 - 6, 0, 360);
		g2d.fillArc(position.x * scale - blockSize, position.y * scale - blockSize, blockSize*2 - 6, blockSize*2 - 6, 0, 360);
		g2d.setComposite(opaque);
		g2d.setClip(null);
		g2d.setColor(Color.BLACK);
		g2d.drawRect(position.x * scale, position.y * scale, blockSize - 1,blockSize - 1);
		
	}
	
	// Returns the color associated with a character.
	// Example: 'R' corresponds to the color RED.
	private Color getColorFromChar(char c) {
		Color color;
		switch (c) {
		case 'B':
			color = Color.BLUE;
			break;
		case 'C':
			color = Color.CYAN;
			break;
		case 'M':
			color = Color.MAGENTA;
			break;
		case 'Y':
			color = Color.YELLOW;
			break;
		case 'O':
			color = Color.ORANGE;
			break;
		case 'G':
			color = Color.GREEN;
			break;
		case 'R':
			color = Color.RED;
			break;
		case 'X':
			color = LightBlue;
			break;
		default:
			color = Color.BLACK;
			break;
		}
		return color.darker();
	}
	
	// Modifies the current tetrominoes x and y coordinates with the x and y offset values
	private void initBlockPositions() {
		int[][] tetrominoMatrix = this.tetromino.getShapeMatrix();
		int count = 0;
		for (int row=0;row<tetrominoMatrix.length;row++) {
			for (int col=0;col<tetrominoMatrix[row].length;col++) {
				if (tetrominoMatrix[row][col] == 1) {
					if (blockPositions[count] == null) {
						blockPositions[count] = new Point();
					}
					blockPositions[count].x = col + this.xPos - tetromino.getLeftWhiteSpace();
					blockPositions[count].y = row - tetromino.getTopWhiteSpace() + this.yPos-1;
					count++;
				}
			}
		}
	}
	
	
	// The main game loop.
	// Algorithm:
	// If the game has not yet started or the game is paused or the game is over then sleep
	// If there is no active tetromino then add one to the game and produce the next one
	// If the tetromino can move down then move it down by one block position
	// If not, then check to see if the game is over
	// If the game is not over save the tetromino in the matrix and search for solid rows
	// Update the view with the current scene and sleep.
	class GameLoop implements Runnable {
		boolean gameOver = false;
		boolean gameStarted = false;
		boolean paused = false;
		public void triggerGame() {
			gameStarted = !gameStarted;
			if (gameStarted) {
				audioManager.startMusic();
				gameOver = false;
			} else {
				audioManager.stopMusic(false);
				view.resetButton();
				paused = false;
				Main.difficultyMenu.setEnabled(true);
			}
			generateScene();
		}
		public void pause() {
			paused = !paused;
			if (!paused) {
				audioManager.startMusic();
			} else {
				audioManager.stopMusic(true);
			}
		}
		public boolean isPaused() {
			return paused;
		}
		public boolean isGameOver() {
			return gameOver;
		}
		public boolean isGameStarted() {
			return gameStarted;
		}
		public void run() {
			long startTime = 0;
			long endTime = 0;
			while (true) {
				while (!gameStarted || paused || gameOver) {
					generateScene();
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if (!tetrominoActive) {
					yPos = 0;
					tetromino = nextTetromino;
					// If the difficulty is set to insane let's
					if (difficulty == 'I') {
						xPos = (int)(Math.random() * (NUM_BLOCKS_X - tetromino.getMaxWidth() + 1)) ;
					} else {
						xPos = DEFAULT_X;
					}
					nextTetromino = tetrominoFactory.selectTetromino(score);
					initBlockPositions();
					tetrominoActive = true;
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if (canTetrominoMoveDown()) {
					incrementPosY();
				} else {
					if (yPos<=0) {
						gameOver = true;
						tetrominoActive = false;
						triggerGame();
					} else {
						addTetrominoToMatrix();
						tetrominoActive = false;
						searchAndDestroy();
					}
				}
				generateScene();
				int sleepDuration;
				switch (difficulty) {
				case 'M':
					sleepDuration = MEDIUM_DELAY;
					break;
				case 'H':
					sleepDuration = HARD_DELAY;
					break;
				case 'I':
					sleepDuration = INSANE_DELAY;
					break;
				default:
					sleepDuration = EASY_DELAY;
				}
				startTime = System.currentTimeMillis();
				endTime = startTime;
				while (endTime - startTime < sleepDuration-speedUp) {
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
						break;
					}
					endTime = System.currentTimeMillis();
				}
			}
		}
	}

}
